/**
 * 模板字符串标签函数，永远不要用括号的形式来调用
 * @param {Array} strings 字符串片段
 * @param  {...any} expressions 表达式片段
 * @returns {String} 低阶脚本
 */
function string6(strings, ...expressions) {

  //低阶脚本输出流  
  let lowLevelScript = [];  

  //插槽函数
  let echo = (a, ...b) => lowLevelScript.push(string6(a, ...b))

  for (let i = 0; i < strings.length; i++) {

    //取出文本片段，并运行TEXT_HOOKS
    let text = strings[i];
    for(let item of STRING6_TEXT_HOOKS){
      if(item.pattern(text)){
        text = item.executer(text);
        break;
      }
    }
    lowLevelScript.push(text);

    if (i < expressions.length) {

      let exp = expressions[i];

      //如果表达式片段是一个匿名函数，则执行插槽回写逻辑
      if(exp instanceof Function && !exp.name){
        //此处的exp可以是一个箭头函数，也可以是一个普通的的匿名函数
        //普通匿名函数中可以用this来代替echo，箭头函数中无法用this
        exp.call(echo, echo);
      }

      else{

        //如果EXP_HOOKS捕获到了表达式片段，则由hook的executer来翻译表达式，并追加到低阶脚本流
        let catched = false;
        for(let item of STRING6_EXP_HOOKS){
          if(item.pattern(exp)){
            lowLevelScript.push(item.executer(exp));
            catched=true;
            break;
          }
        }

        //如果表达式片段没有被EXP_HOOKS捕获到，则直接将表达式的值追加到低阶脚本流
        if(!catched){
          lowLevelScript.push(exp);
        }

      }
      
    }
  }
  
  let reg = /\n\s+\n/g;
  let result = lowLevelScript.join("")
  result = result.replace(reg,"\n");

  return result;

}

/**
 * 所有的文本hook都push到这个数组中
 */
const STRING6_TEXT_HOOKS = [];

/**
 * 所有的表达式hook都push到这个数组中
 */
const STRING6_EXP_HOOKS = [];


/**
  当低阶脚本的大括号与高阶脚本交织严重时，可在低阶脚本中用<brackets-hook>代替左大括号,用</brackets-hook>代替右大括号。
  注意只有当低阶脚本的大括号严重影响到阅读时才使用这个hook，通常只在离高阶脚本的大括号最近的地方使用这个hook

  示例如下
  <script type="text/javascript">
    let json = [
    ${
      (echo)=>{
        for(let i=0;i<data.length;i++){
          let item = data[i]
          echo
          `
      <brackets-hook>
        id:${item.id},
        name:"${item.name}",
        sex:${item.sex}
      </brackets-hook>`
          if(i<data.length-1) echo`,`
        }
      }
    }
    ];
    console.log(json);
  </script>
  */
const brackets_hook ={
  pattern:text=>{
    return text.constructor===String 
            && (text.indexOf("<brackets-hook>")>=0
              ||text.indexOf("</brackets-hook>")>=0)
  },
  executer:text=>{
    const regLeftBrackets = /<\s*brackets-hook\s*>/g;
    const regRightBrackets = /<\s*\/brackets-hook\s*>/g;
    let result = text.replace(regLeftBrackets,"{")
      .replace(regRightBrackets,"}");
    return result;
  }
}

/*
  这是作者精心设计的一个用来处理高阶脚本中if语句块的hook，供hook开发人员参考。
  捕获条件：
    1：输入的表达式是一个数组
    2：数组的长度为3
    3：数组的第0个元素是一个正则表达式，且该正则能捕获到"if-hook"这个字符串。
        这里用一个正则没有别的意思，仅仅是因为它很酷，很容易从它周边的环境中识别出来。
        hook是一种极限文化，提倡用各种稀奇古怪的办法来解决问题。
    4：数组的第2个元素是一个简单json
  捕获后的处理逻辑如下：
    取数组的第1个元素为条件变量
    遍历第2个元素的key，若key==条件变量(注意这里是用双等号做宽松判定)，则向低阶脚本流输出value

  使用hook前的代码示例：

  let users = [{ id: 1, name: "张向明", sex: 1 }, { id: 2, name: "蒋海艳", sex: 0 }];
  let html = string6`
    <ul>
      ${(echo) => {
        for (let item of users) {
          echo`
            <li id="${item.id}">
              姓名：${item.name}，
              性别：${(echo) => {
                      if (item.sex === 0) {
                        echo`女`
                      } else {
                        echo`男`
                      }
                    }
                  }
            </li>`
        }
      }
    }
    </ul>
      `

  使用hook后的代码示例：
  let users = [{ id: 1, name: "张向明", sex: 1 }, { id: 2, name: "蒋海艳", sex: 0 }];
  let html = string6`
    <ul>
      ${echo => {
        for (let item of users) 
          echo`
            <li id="${item.id}">
              姓名：${item.name}，
              性别：${
                [
                  /if-hook/g, 
                  item.sex, { 0: "女", 1: "男" }
                ]
              }
            </li>`
      }
    }
    </ul>
      `
  */
const if_hook ={

  pattern: exp =>
    exp.constructor === Array
    && exp.length === 3
    && exp[0].constructor === RegExp
    && exp[0].test("if-hook")
    && exp[2].constructor === Object
  ,

  executer: exp => {
    let lowLevelScript = "";
    let pattern = exp[1];
    let body = exp[2];
    for (let key in body) {
      if (key == pattern) {
        lowLevelScript += body[key];
      }
    }
    return lowLevelScript;
  }
}

STRING6_TEXT_HOOKS.push(brackets_hook);
STRING6_EXP_HOOKS.push(if_hook);

